"""
Stuff to do with bounding boxes.
"""
__all__ = ["BoundingBox"]

from OpenGL.GL import *
from math import *
import matrix

class BoundingBox(object):
    """A Bounding Box. Used to approximate the locus of a graphic object."""

    def __init__ (self, center=(0,0,0), side=(0,0,0)):
        """Constructor
        @param center: Center of the box
        @param side: Side length for each of the coordinate axes
        """
        self.center = center
        self.side = side

    def radius(self):
        """Returns the half the size of a diagonal of the box.
        @return: radius of the box's bounding sphere."""
        return sqrt(sum([x*x/4.0 for x in self.side]))

    def vertices (self):
        """Computes the box vertices.
        @return: an array with 8 vertices. Assuming index i corresponds to
        binary digits bz,by,bx, where a bit bk is 0 for the min value in that
        coordinate or 1 for the max value. Thus, for instance, index 6 = 110
        corresponds to vertex (zmax, ymax, xmin).
        """
        halfx,halfy,halfz = map(lambda a: a/2.0, self.side)
        cx,cy,cz = self.center
        return ((cx-halfx,cy-halfy,cz-halfz),
                (cx+halfx,cy-halfy,cz-halfz),
                (cx-halfx,cy+halfy,cz-halfz),
                (cx+halfx,cy+halfy,cz-halfz),
                (cx-halfx,cy-halfy,cz+halfz),
                (cx+halfx,cy-halfy,cz+halfz),
                (cx-halfx,cy+halfy,cz+halfz),
                (cx+halfx,cy+halfy,cz+halfz))

    def draw(self):
        """Draws the box in wireframe."""
        print "desenhando bounding box"
        vtx = self.vertices()
        glBegin (GL_LINE_LOOP)
        for i in (0,1,3,2,0,4,5,7,6,4): glVertex3fv(vtx[i])
        glEnd ()
        glBegin (GL_LINES)
        for i,j in ((2,6),(3,7),(1,5)):
            glVertex3fv (vtx[i])
            glVertex3fv (vtx[j])
        glEnd()

    def __add__(self, box):
        """Computes the sum of two bounding boxes. i.e.,
        the bounding box of the union of two bounding boxes."""
        mina = map (lambda i: self.center[i]-self.side[i]/2.0, range(3))
        minb = map (lambda i: box.center[i]-box.side[i]/2.0, range(3))
        maxa = map (lambda i: self.center[i]+self.side[i]/2.0, range(3))
        maxb = map (lambda i: box.center[i]+box.side[i]/2.0, range(3))
        minvtx = map (min, mina,minb)
        maxvtx = map (max, maxa,maxb)
        return BoundingBox(map (lambda a,b: (a+b)/2.0, minvtx,maxvtx),
                           map (lambda a,b: a-b, minvtx,maxvtx))

    @staticmethod
    def fromPoints (pts):
        """Returns a bounding box for a collection of points.
        @param pts: a sequence of points (x,y,z).
        """
        minvtx = reduce (lambda a,b: map(min, a, b), pts, (1e100,1e100,1e100))
        maxvtx = reduce (lambda a,b: map(max, a, b), pts, (-1e100,-1e100,-1e100))
        return BoundingBox (map (lambda a,b: (a+b)/2.0, minvtx,maxvtx),
                           map (lambda a,b: b-a, minvtx,maxvtx))

    def transform (self,m):
        """Returns a transformed version of this bounding box.
        @param m: an opengl transformation matrix stored as an array with 16
        floats.
        @return: a BoundingBox.
        """
        return BoundingBox.fromPoints (
            map (lambda p: matrix.gltransformpoint (m, p), self.vertices()))

if __name__=="__main__":
    # A simple test
    box = BoundingBox()
